package org.libsdl.app;

import javax.microedition.khronos.egl.*;
import javax.microedition.khronos.opengles.GL10;

import android.content.*;
import android.view.*;
import android.util.Log;
import android.graphics.*;
import android.hardware.*;

/**
 * SDLSurface. This is what we draw on, so we need to know when it's created
 * in order to do anything useful. 
 * Because of this, that's where we set up the SDL thread
 * 
 * Based on SDL sample, modified by Nicolas GUILLAUME (added offset, scale support & co)
 * @see SDLActivity
 */
public class SDLSurface
    extends SurfaceView
    implements SurfaceHolder.Callback, SensorEventListener 
{

    // This is what SDL runs in. It invokes SDL_main(), eventually
    private Thread mSDLThread;    

    // EGL private objects
    private EGLContext  mEGLContext;
    private EGLSurface  mEGLSurface;
    private EGLDisplay  mEGLDisplay;
        
    private volatile short offsetX = 0, offsetY = 0;
    private volatile float scale = 1.0f;
    private volatile boolean offsetOrScaleChanged = false;
    
    protected volatile short viewportW = -1, viewportH = -1;

    // Sensors
    private static SensorManager mSensorManager;

    // Startup    
    public SDLSurface(Context context)
    {
        super(context);
        getHolder().addCallback(this); 

        setFocusable(true);
        setFocusableInTouchMode(true);
        requestFocus();

        mSensorManager = (SensorManager)context.getSystemService("sensor");  
    }
   
    
    /**
     * Set offset and scale
     * @return true on success
     */
    public synchronized final boolean setOffsetAndScale(int xOffset, int yOffset, float scale)
    {
        if(offsetOrScaleChanged)
            return false;
        
        this.offsetX = (short)xOffset;
        this.offsetY = (short)yOffset;
        this.scale = scale;
        
        //this is atomic
        offsetOrScaleChanged = true;
        return true;
    }
    
    /**
     * Reset original viewport.
     * @return true on success
     */
    public synchronized final boolean resetOffsetAndScale()
    {
        if(offsetOrScaleChanged)
            return false;
        
        this.scale = Float.NaN; // will cause a reset in JNI wrapper
        
        // this is atomic
        offsetOrScaleChanged = true;
        return true;
    }
    
    /**
     * Convert coord from screen to current view (scale & offset)
     */
    public final float screenToViewX(float x)
    {
        return (x - offsetX) / scale;
    }

    /**
     * Convert coord from screen to current view (scale & offset)
     */
    public final float screenToViewY(float y)
    {
        return (y  - offsetY) / scale;
    }
    
    
    /** Synchronous mouse simulation.
     * Coord are from surface point of view.
     * This is equivalent to SDLActivity.sendMouseEvent(action, button, screenToViewportX(x), screenToViewportY(y), p)
     * @param action MoveEvent.ACTION_*
     * @param button SDLActivity.MOUSE_BUTTON_*
     * @param p optional pressure from 0f to 1f.
     * @see SDLSurface.sendMouseEvent()
     * @see SDLSurface.screenToViewportX()
     * @see SDLSurface.screenToViewportY()
     */
    public final void sendMouseEvent(int action, int button, float x, float y, float p)
    {
        SDLActivity.sendMouseEvent(action, button, screenToViewX(x), screenToViewY(y), p);
    }

    
    // Called when we have a valid drawing surface
    @Override
    public void surfaceCreated(SurfaceHolder holder)
    {
        //Log.v("SDL", "surfaceCreated()");

        enableSensor(Sensor.TYPE_ACCELEROMETER, true);
    }

    // Called when we lose the surface
    @Override
    public void surfaceDestroyed(SurfaceHolder holder)
    {
        //Log.v("SDL", "surfaceDestroyed()");

        // Send a quit message to the application
        SDLActivity.nativeQuit();

        // Now wait for the SDL thread to quit
        if (mSDLThread != null)
        {
            try
            {
                mSDLThread.join();
            }
            catch(Exception e)
            {
                Log.v("SDL", "Problem stopping thread: " + e);
            }
            mSDLThread = null;

            //Log.v("SDL", "Finished waiting for SDL thread");
        }

        enableSensor(Sensor.TYPE_ACCELEROMETER, false);
    }

    // Called when the surface is resized
    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height)
    {
        //Log.v("SDL", "surfaceChanged()");

        int sdlFormat = 0x85151002; // SDL_PIXELFORMAT_RGB565 by default
        switch (format)
        {
            case PixelFormat.A_8:
                Log.v("SDL", "pixel format A_8");
                break;
            case PixelFormat.LA_88:
                Log.v("SDL", "pixel format LA_88");
                break;
            case PixelFormat.L_8:
                Log.v("SDL", "pixel format L_8");
                break;
            case PixelFormat.RGBA_4444:
                Log.v("SDL", "pixel format RGBA_4444");
                sdlFormat = 0x85421002; // SDL_PIXELFORMAT_RGBA4444
                break;
            case PixelFormat.RGBA_5551:
                Log.v("SDL", "pixel format RGBA_5551");
                sdlFormat = 0x85441002; // SDL_PIXELFORMAT_RGBA5551
                break;
            case PixelFormat.RGBA_8888:
                Log.v("SDL", "pixel format RGBA_8888");
                sdlFormat = 0x86462004; // SDL_PIXELFORMAT_RGBA8888
                break;
            case PixelFormat.RGBX_8888:
                Log.v("SDL", "pixel format RGBX_8888");
                sdlFormat = 0x86262004; // SDL_PIXELFORMAT_RGBX8888
                break;
            case PixelFormat.RGB_332:
                Log.v("SDL", "pixel format RGB_332");
                sdlFormat = 0x84110801; // SDL_PIXELFORMAT_RGB332
                break;
            case PixelFormat.RGB_565:
                Log.v("SDL", "pixel format RGB_565");
                sdlFormat = 0x85151002; // SDL_PIXELFORMAT_RGB565
                break;
            case PixelFormat.RGB_888:
                Log.v("SDL", "pixel format RGB_888");
                // Not sure this is right, maybe SDL_PIXELFORMAT_RGB24 instead?
                sdlFormat = 0x86161804; // SDL_PIXELFORMAT_RGB888
                break;
            default:
                Log.v("SDL", "pixel format unknown " + format);
                break;
        }
        SDLActivity.onNativeResize(width, height, sdlFormat);

        // Now start up the C app thread
        if (mSDLThread == null)
        {
            mSDLThread = new Thread(new SDLMain(), "SDLThread"); 
            mSDLThread.start();       
        }
    }

    // unused
    @Override
    public void onDraw(Canvas canvas) {}


    // EGL functions
    public boolean initEGL(int majorVersion, int minorVersion)
    {
        Log.v("SDL", "Starting up OpenGL ES " + majorVersion + "." + minorVersion);

        try
        {
            final EGL10 egl = (EGL10)EGLContext.getEGL();

            EGLDisplay dpy = egl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);

            final int[] version = new int[2];
            egl.eglInitialize(dpy, version);

            final int EGL_OPENGL_ES_BIT = 1;
            final int EGL_OPENGL_ES2_BIT = 4;
            int renderableType = 0;
            
            if (majorVersion == 2)
                renderableType = EGL_OPENGL_ES2_BIT;
            else if (majorVersion == 1)
                renderableType = EGL_OPENGL_ES_BIT;
            
            final int[] configSpec =
                {
                        //EGL10.EGL_DEPTH_SIZE,   16,
                        EGL10.EGL_RENDERABLE_TYPE, renderableType,
                        EGL10.EGL_NONE
                };
            
            final EGLConfig[] configs = new EGLConfig[1];
            final int[] num_config = new int[1];
            if (!egl.eglChooseConfig(dpy, configSpec, configs, 1, num_config) || num_config[0] == 0)
            {
                Log.e("SDL", "No EGL config available");
                return false;
            }
            EGLConfig config = configs[0];

            // PATCH
            final int EGL_CONTEXT_CLIENT_VERSION = 0x3098;
            final int contextAttrs[] = 
                {
                    EGL_CONTEXT_CLIENT_VERSION, majorVersion,
                    EGL10.EGL_NONE
                };
            final EGLContext ctx = egl.eglCreateContext(dpy, config, EGL10.EGL_NO_CONTEXT, /*null*/contextAttrs);
            if (ctx == EGL10.EGL_NO_CONTEXT)
            {
                Log.e("SDL", "Couldn't create context");
                return false;
            } 

            final EGLSurface surface = egl.eglCreateWindowSurface(dpy, config, this, null);
            if (surface == EGL10.EGL_NO_SURFACE)
            {
                Log.e("SDL", "Couldn't create surface");
                return false;
            }

            if (!egl.eglMakeCurrent(dpy, surface, surface, ctx))
            {
                Log.e("SDL", "Couldn't make context current");
                return false;
            }
            
            mEGLContext = ctx;
            mEGLDisplay = dpy;
            mEGLSurface = surface;
        }
        catch(Exception e)
        {
            Log.v("SDL", e + "");
            for (StackTraceElement s : e.getStackTrace())
                Log.v("SDL", s.toString());
        }

        return true;
    }

    // EGL buffer flip
    public void flipEGL()
    {
        //called from SDL thread
        try
        {           
            final EGL10 egl = (EGL10)EGLContext.getEGL();
            
            egl.eglWaitNative(EGL10.EGL_NATIVE_RENDERABLE, null);            
            // drawing here
            
            egl.eglWaitGL();            
            egl.eglSwapBuffers(mEGLDisplay, mEGLSurface);

            if(viewportW < 0)
                updateViewport();
            
            // update in SDL thread to avoid problems
            if(offsetOrScaleChanged)
            {
                SDLActivity.nativeSetOffsetAndScale(offsetX, offsetY, scale);
                offsetOrScaleChanged = false;
            }
            
        }
        catch(Exception e)
        {
            Log.v("SDL", "flipEGL(): " + e);
            for (StackTraceElement s : e.getStackTrace())
                Log.v("SDL", s.toString());
        }
    }
    
    private void updateViewport()
    {
        int [] v = new int[4]; 
        SDLActivity.nativeGetRendererViewport(v);
        viewportW = (short)v[2];
        viewportH = (short)v[3];
    }
    
    @Override
    public boolean onKeyDown(int keyCode, KeyEvent e)
    {
        SDLActivity.onNativeKeyDown(keyCode);
        return true;
    }
    @Override
    public boolean onKeyUp(int keyCode, KeyEvent e)
    {
        SDLActivity.onNativeKeyUp(keyCode);
        return true;
    }
    @Override
    public boolean onTouchEvent(MotionEvent e)
    {
        final int action = e.getAction();
        final float x = e.getX();
        final float y = e.getY();
        final float p = e.getPressure();
        SDLActivity.onNativeTouch(action, screenToViewX(x), screenToViewY(y), p);
        return true;
    }

    // Sensor events
    public void enableSensor(int sensortype, boolean enabled)
    {
        // TODO: This uses getDefaultSensor - what if we have >1 accels?
        if (enabled)
            mSensorManager.registerListener(this, 
                    mSensorManager.getDefaultSensor(sensortype), 
                    SensorManager.SENSOR_DELAY_GAME, null);
        else
            mSensorManager.unregisterListener(this, mSensorManager.getDefaultSensor(sensortype));
    }

    @Override
    public void onAccuracyChanged(Sensor sensor, int accuracy)
    {
        // TODO
    }

    @Override
    public void onSensorChanged(SensorEvent event)
    {
        if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER)
        {
            SDLActivity.onNativeAccel(event.values[0],
                    event.values[1],
                    event.values[2]);
        }
    }

}

/**
  * Simple nativeInit() runnable
  */
class SDLMain implements Runnable
{
    @Override
    public void run()
    {
        // Runs SDL_main()
        SDLActivity.nativeInit();

        //Log.v("SDL", "SDL thread terminated");
    }
}
